package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// Initializes an OTLP exporter, and configures the corresponding trace and
// metric providers.
func initProvider() func() {
	hostPort := net.JoinHostPort(datakitURL, datakitPort)
	fmt.Println("host = " + hostPort)

	ctx := context.Background()

	res, err := resource.New(ctx,
		resource.WithAttributes(
			// the service name used to display traces in backends
			semconv.ServiceNameKey.String("ServerName"),
			// semconv.FaaSIDKey.String(""),
		),
		//resource.WithOS(), // and so on ...
	)

	handleErr(err, "failed to create resource")
	var bsp sdktrace.SpanProcessor

	// If the OpenTelemetry Collector is running on a local cluster (minikube or
	// microk8s), it should be accessible through the NodePort service at the
	// `localhost:30080` endpoint. Otherwise, replace `localhost` with the
	// endpoint of your cluster. If you run the app inside k8s, then you can
	// probably connect directly to the service through dns
	conn, err := grpc.DialContext(ctx, hostPort, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
	handleErr(err, "failed to create gRPC connection to collector")
	// Set up a trace exporter
	traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
	handleErr(err, "failed to create trace exporter")

	bsp = sdktrace.NewBatchSpanProcessor(traceExporter)

	// Register the trace exporter with a TracerProvider, using a batch
	// span processor to aggregate spans before export.
	tracerProvider := sdktrace.NewTracerProvider(
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
		sdktrace.WithResource(res),
		sdktrace.WithSpanProcessor(bsp),
	)
	otel.SetTracerProvider(tracerProvider)

	// set global propagator to tracecontext (the default is no-op).
	otel.SetTextMapPropagator(propagation.TraceContext{})

	return func() {
		// Shutdown will flush any remaining spans and shut down the exporter.
		handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown TracerProvider")
		time.Sleep(time.Second)
	}
}

var tracer = otel.Tracer("tracer_user_login")

// web handler 处理请求数据
func user(w http.ResponseWriter, r *http.Request) {
	// ... 接收客户端请求
	log.Println("doing user")
	// labels represent additional key-value descriptors that can be bound to a
	// metric observer or recorder.
	commonLabels := []attribute.KeyValue{attribute.String("project", "otel-agent-golang")}
	// work begins
	ctx, span := tracer.Start(
		context.Background(),
		"span-Example",
		trace.WithAttributes(commonLabels...))
	defer span.End()
	<-time.After(time.Millisecond * 50)
	service(ctx)

	log.Printf("Doing really hard work")
	<-time.After(time.Millisecond * 40)

	log.Printf("Done!")
	w.Write([]byte("ok"))
}

// service 调用 service 层处理业务
func service(ctx context.Context) {
	log.Println("service")
	ctx1, iSpan := tracer.Start(ctx, "Sample-service")
	<-time.After(time.Second / 2) // do something...
	dao(ctx1)
	iSpan.End()
}

// dao 数据访问层
func dao(ctx context.Context) {
	log.Println("dao")
	ctxD, iSpan := tracer.Start(ctx, "Sample-dao")
	<-time.After(time.Second / 2)

	// 创建子 span 查询数据库等操作
	_, sqlSpan := tracer.Start(ctxD, "do_sql")
	sqlSpan.SetStatus(codes.Ok, "is ok") //
	<-time.After(time.Second)
	sqlSpan.End()

	iSpan.End()
}

func handleErr(err error, message string) {
	if err != nil {
		log.Fatalf("%s: %v", message, err)
	}
}

var (
	datakitURL, datakitPort string
)

func main() {
	datakitURL = os.Getenv("DATAKIT_HOST")
	datakitPort = os.Getenv("DATAKIT_PORT")

	if len(datakitURL) == 0 {
		fmt.Println("datakit URL is empty")
		return
	}

	if len(datakitPort) == 0 {
		fmt.Println("datakit port is empty")
		return
	}

	shutdown := initProvider()
	defer shutdown()
	log.Println("connect ...")
	http.HandleFunc("/user", user)
	go handleErr(http.ListenAndServe(":8080", nil), "open server")
	time.Sleep(time.Minute * 2)
	os.Exit(0)
}
